home *** CD-ROM | disk | FTP | other *** search
- // By Judy D. Halchin, Educational Computing Services, Allegheny College.
- // You may freely copy, distribute and reuse this code.
- // Allegheny College and the author disclaim any warranty of any kind,
- // expressed or implied, as to its fitness for any particular use.
- // This work was partially supported by a Teacher Preparation grant from the
- // National Science Foundation.
-
- #import "NumberLine.h"
- #import "drawLine.h"
- #import <appkit/NXImage.h>
- #import <appkit/graphics.h>
- #import <dpsclient/wraps.h>
- #import <appkit/nextstd.h>
-
- @implementation NumberLine
-
- float mantissa(float floatValue);
- int orderOfMag(float floatValue);
- int round(float floatValue);
-
- - initFrame:(NXRect *)frameRect
- {
- [super initFrame:frameRect];
-
- //initialize all instance variables
- linePosition = 30;
- if (bounds.size.width >= bounds.size.height)
- orientation = HORIZONTAL;
- else
- orientation = VERTICAL;
- labelPosition = BELOWLEFT;
-
- backgroundGray = NX_WHITE;
- lineGray = NX_BLACK;
- labelGray = NX_BLACK;
-
- borderWidth = 0;
- endBars = NO;
-
- //make an NXImage to hold the number line
- numberLineNXImage = [[NXImage allocFromZone:[self zone]]
- initSize:&bounds.size];
-
- //if orientation is vertical, rotate to match the number line
- if (orientation == VERTICAL)
- {
- [self rotate:90];
- [self translate:0 :-bounds.size.height];
- }
-
- //scale and translate
- [self scale:bounds.size.width/20 :1];
- [self translate:10 :linePosition];
-
- //calculate the tick marks and draw the line in the NXImage
- [self calculateTicks];
- [self drawIntoImage];
-
- return self;
- }
-
- - drawSelf:(const NXRect *)rects :(int)rectCount
- {
- NXPoint compositePoint;
-
- compositePoint.x = bounds.origin.x;
- if (orientation == HORIZONTAL)
- compositePoint.y = bounds.origin.y;
- else
- compositePoint.y = bounds.origin.y + bounds.size.height;
- PSsetgray(1.0);
- NXRectFill(&bounds);
- [numberLineNXImage composite:NX_COPY toPoint:&compositePoint];
- return self;
- }
-
- - drawIntoImage
- {
- NXSize imageSize;
- float scaleFactor, unscaleFactor, length;
-
- [numberLineNXImage getSize:&imageSize];
- length = MAX(imageSize.width, imageSize.height);
- scaleFactor = length / bounds.size.width;
- unscaleFactor = bounds.size.width / length;
-
- [numberLineNXImage lockFocus];
-
- drawBorders(imageSize.width, imageSize.height, linePosition, borderWidth,
- backgroundGray, lineGray, orientation, endBars);
-
- drawLine(bounds.origin.x, bounds.origin.y, bounds.size.width,
- bounds.size.height, scaleFactor, orientation, lineGray);
-
- drawTicks(bounds.origin.x, bounds.origin.y, bounds.size.height,
- scaleFactor, orientation, lineGray, tickInfo.firstTick,
- tickInfo.tickSpacing, tickInfo.firstLabel,
- tickInfo.labelSpacing, tickInfo.numberOfTicks);
-
- if (orientation == HORIZONTAL)
- drawHorzLabels(bounds.origin.x, bounds.origin.y, scaleFactor,
- unscaleFactor, labelPosition, labelGray,
- tickInfo.firstLabel, tickInfo.labelSpacing,
- tickInfo.scientific, tickInfo.numberOfLabels,
- tickInfo.labelString[0],
- tickInfo.labelString[1], tickInfo.labelString[2],
- tickInfo.labelString[3], tickInfo.labelString[4],
- tickInfo.labelString[5], tickInfo.labelString[6],
- tickInfo.labelString[7], tickInfo.labelString[8],
- tickInfo.labelString[9], tickInfo.labelString[10],
- tickInfo.expString[0], tickInfo.expString[1],
- tickInfo.expString[2], tickInfo.expString[3],
- tickInfo.expString[4], tickInfo.expString[5],
- tickInfo.expString[6], tickInfo.expString[7],
- tickInfo.expString[8], tickInfo.expString[9],
- tickInfo.expString[10]);
- else
- drawVertLabels(bounds.origin.x, bounds.origin.y, bounds.size.height,
- scaleFactor, unscaleFactor, labelPosition, labelGray,
- tickInfo.firstLabel, tickInfo.labelSpacing,
- tickInfo.scientific, tickInfo.numberOfLabels,
- tickInfo.labelString[0],
- tickInfo.labelString[1], tickInfo.labelString[2],
- tickInfo.labelString[3], tickInfo.labelString[4],
- tickInfo.labelString[5], tickInfo.labelString[6],
- tickInfo.labelString[7], tickInfo.labelString[8],
- tickInfo.labelString[9], tickInfo.labelString[10],
- tickInfo.expString[0], tickInfo.expString[1],
- tickInfo.expString[2], tickInfo.expString[3],
- tickInfo.expString[4], tickInfo.expString[5],
- tickInfo.expString[6], tickInfo.expString[7],
- tickInfo.expString[8], tickInfo.expString[9],
- tickInfo.expString[10]);
-
- [numberLineNXImage unlockFocus];
-
- return self;
- }
-
- - numberLineNXImage
- {
- return numberLineNXImage;
- }
-
- - (float)max
- {
- return bounds.origin.x + bounds.size.width;
- }
-
- - (float)min
- {
- return bounds.origin.x;
- }
-
- - setMin:(float)newMin max:(float)newMax
- {
- //return if input is invalid
- if (newMin >= newMax)
- return self;
-
- //perform the translation and rotation
- [super scale:bounds.size.width/(newMax-newMin) :1];
- [super translate:bounds.origin.x - newMin :0];
-
- //recalculate ticks and redraw into NXImage
- [self calculateTicks];
- [self drawIntoImage];
-
- //display if we're supposed to
- if (!vFlags.disableAutodisplay)
- [self display];
-
- return self;
- }
-
- - zoomIn:(float)amount
- {
- float center, halfRange;
-
- if (amount > 0)
- {
- center = bounds.origin.x + bounds.size.width/2;
- halfRange = bounds.size.width/amount/2;
- [self setMin:center - halfRange max:center + halfRange];
- }
- return self;
- }
-
- - zoomOut:(float)amount
- {
- float center, halfRange;
-
- if (amount > 0)
- {
- center = bounds.origin.x + bounds.size.width/2;
- halfRange = bounds.size.width*amount/2;
- [self setMin:center - halfRange max:center + halfRange];
- }
- return self;
- }
-
- - (float)center
- {
- return bounds.origin.x + bounds.size.width / 2;
- }
-
- - setCenter:(float)newCenter
- {
- float translation;
-
- translation = newCenter - (bounds.origin.x + bounds.size.width / 2);
- [self setMin:bounds.origin.x + translation
- max:bounds.origin.x + bounds.size.width + translation];
- return self;
- }
-
- - slide:(float)translation
- {
- [self setMin:bounds.origin.x + translation
- max:bounds.origin.x + bounds.size.width + translation];
- return self;
- }
-
- - (float)linePosition
- {
- return linePosition;
- }
-
- - setLinePosition:(float)newPosition
- {
- [self translate:0 :newPosition - linePosition];
- linePosition = newPosition;
- [self drawIntoImage];
- return self;
- }
-
- - setBackgroundGray:(float)gray
- {
- backgroundGray = gray;
- [self drawIntoImage];
- return self;
- }
-
- - setLineGray:(float)gray
- {
- lineGray = gray;
- [self drawIntoImage];
- return self;
- }
-
- - setLabelGray:(float)gray
- {
- labelGray = gray;
- [self drawIntoImage];
- return self;
- }
-
- - (float)backgroundGray
- {
- return backgroundGray;
- }
-
- - (float)lineGray
- {
- return lineGray;
- }
-
- - (float)labelGray
- {
- return labelGray;
- }
-
-
- - (int)orientation
- {
- return orientation;
- }
-
- - (int)labelPosition
- {
- return labelPosition;
- }
-
- - setLabelPosition:(int)newPosition
- {
- labelPosition = newPosition;
- return self;
- }
-
- - setBordered:(BOOL)shouldHaveBorder width:(float)width
- {
- if (shouldHaveBorder)
- borderWidth = width;
- else
- borderWidth = -1;
- [self drawIntoImage];
- return self;
- }
-
- - (BOOL)bordered
- {
- if (borderWidth == -1)
- return NO;
- else
- return YES;
- }
-
- - setEndBars:(BOOL)shouldHaveEndBars
- {
- endBars = shouldHaveEndBars;
- [self drawIntoImage];
- return self;
- }
-
- - (BOOL)hasEndBars
- {
- return endBars;
- }
-
- - calculateTicks
- {
- float rawValue, lastLabel, labelValue, tick, length;
- int orderSpacing, orderFirstLast, maxOrder;
- int labelsGoal, spaceAvail, digits, digitsRight=0, i, nonblank;
- char formatString[20], rawLabel[30], *ePointer;
- BOOL spacing25, labelsOK;
- NXSize imageSize;
-
- //calulate the ideal number of labels desired
- [numberLineNXImage getSize:&imageSize];
- length = MAX(imageSize.width, imageSize.height);
- labelsGoal = (int)(length / 100);
- if (labelsGoal < 3)
- labelsGoal = 3;
-
- //propose a label spacing
- rawValue = mantissa(bounds.size.width) / labelsGoal;
-
- spacing25 = NO;
- if (rawValue <= 0.128)
- tickInfo.labelSpacing = pow(10, orderOfMag(bounds.size.width)-1);
- else if ((0.128 < rawValue) && (rawValue <= 0.25))
- tickInfo.labelSpacing = 2 * pow(10, orderOfMag(bounds.size.width)-1);
- else if ((0.25 < rawValue) && (rawValue <= 0.35))
- {
- tickInfo.labelSpacing = 2.5 *pow(10,orderOfMag(bounds.size.width)-1);
- spacing25 = YES;
- }
- else if ((0.35 < rawValue) && (rawValue <= 0.65))
- tickInfo.labelSpacing = 5 * pow(10, orderOfMag(bounds.size.width)-1);
- else if ((0.65 < rawValue) && (rawValue <= 1.23))
- tickInfo.labelSpacing = pow(10, orderOfMag(bounds.size.width));
- else if ((1.23 < rawValue) && (rawValue <= 2.5))
- tickInfo.labelSpacing = 2 * pow(10, orderOfMag(bounds.size.width));
- else if (2.5 < rawValue)
- {
- tickInfo.labelSpacing = 2.5 * pow(10, orderOfMag(bounds.size.width));
- spacing25 = YES;
- }
-
- //count the labels the proposed spacing will produce and adjust if nec.
- labelsOK = NO;
- while (!labelsOK)
- {
- //find the first and last labels that will be used and number of labels
- //pick a tentative first label and last label
- tickInfo.firstLabel = floor(bounds.origin.x/tickInfo.labelSpacing)
- * tickInfo.labelSpacing;
- if (tickInfo.firstLabel <= bounds.origin.x)
- tickInfo.firstLabel = tickInfo.firstLabel +
- tickInfo.labelSpacing;
- for (lastLabel = tickInfo.firstLabel;
- lastLabel + tickInfo.labelSpacing <
- bounds.origin.x + bounds.size.width;
- lastLabel = lastLabel + tickInfo.labelSpacing);
- if (orderOfMag(lastLabel) < orderOfMag(tickInfo.firstLabel) - 2)
- {
- orderSpacing = orderOfMag(tickInfo.labelSpacing);
- lastLabel = round(lastLabel * pow(10, -orderSpacing+2)) *
- pow(10, orderSpacing-2);
- }
- //determine number of digits and whether to use scientific notation
- orderSpacing = orderOfMag(tickInfo.labelSpacing);
- if (abs(orderOfMag(tickInfo.firstLabel)) >=
- abs(orderOfMag(lastLabel)))
- orderFirstLast = orderOfMag(tickInfo.firstLabel);
- else
- orderFirstLast = orderOfMag(lastLabel);
-
- if ((abs(orderSpacing) > 3) || (abs(orderFirstLast) > 3))
- tickInfo.scientific = YES;
- else
- {
- digits = orderFirstLast - orderSpacing;
- if (spacing25)
- digits = digits + 1;
- if (digits > 3)
- tickInfo.scientific = YES;
- else
- tickInfo.scientific = NO;
- }
-
- if (tickInfo.scientific)
- {
- digits = orderFirstLast - orderSpacing;
- if (spacing25)
- digits = digits + 1;
- }
- else
- {
- maxOrder = MAX(abs(orderFirstLast), abs(orderSpacing));
-
- if ((orderFirstLast > 0) && (orderSpacing < 0))
- {
- digits = orderFirstLast - orderSpacing;
- digitsRight = - orderSpacing;
- if (spacing25)
- {
- digits = digits + 1;
- digitsRight = digitsRight + 1;
- }
- }
- else
- {
- digits = maxOrder;
- if ((orderSpacing <= 0) && spacing25)
- digits = digits + 1;
- if (orderFirstLast > 0)
- digitsRight = 0;
- else
- digitsRight = maxOrder;
- if (spacing25 && (orderSpacing <= 0))
- digitsRight = digitsRight + 1;
- }
- }
-
- //adjust first and/or last label if there isn't enough space
- spaceAvail = (tickInfo.firstLabel - bounds.origin.x) * length
- / bounds.size.width;
- if (orientation == HORIZONTAL)
- {
- if ((tickInfo.scientific && (spaceAvail < (8+digits)*4)))
- tickInfo.firstLabel = tickInfo.firstLabel +
- tickInfo.labelSpacing;
- if ((!tickInfo.scientific) && (spaceAvail < (2+digits)*4))
- tickInfo.firstLabel = tickInfo.firstLabel +
- tickInfo.labelSpacing;
- }
- else
- if (spaceAvail < 6)
- tickInfo.firstLabel = tickInfo.firstLabel +
- tickInfo.labelSpacing;
-
- spaceAvail = (bounds.origin.x + bounds.size.width - lastLabel)
- * length / bounds.size.width;
-
- if (orientation == HORIZONTAL)
- {
- if (tickInfo.scientific && (spaceAvail < (8+digits)*4))
- lastLabel = lastLabel - tickInfo.labelSpacing;
- if ((!tickInfo.scientific) && (spaceAvail < (2+digits)*4))
- lastLabel = lastLabel - tickInfo.labelSpacing;
- }
- else
- if (spaceAvail < 10)
- lastLabel = lastLabel - tickInfo.labelSpacing;
-
- //count the labels
- tickInfo.numberOfLabels = round((lastLabel -
- tickInfo.firstLabel)/tickInfo.labelSpacing) + 1;
-
- //alter the spacing if the number of labels is not suitable
- if (tickInfo.numberOfLabels >= 3)
- labelsOK = YES;
- else
- {
- if (fabs(mantissa(tickInfo.labelSpacing) - 1) < 0.01)
- tickInfo.labelSpacing = 5 *
- pow(10, orderOfMag(tickInfo.labelSpacing)-1);
- else if (fabs(mantissa(tickInfo.labelSpacing) - 2) < 0.01)
- tickInfo.labelSpacing =
- pow(10, orderOfMag(tickInfo.labelSpacing));
- else if (fabs(mantissa(tickInfo.labelSpacing) - 2.5) < 0.01)
- {
- tickInfo.labelSpacing = 2 *
- pow(10, orderOfMag(tickInfo.labelSpacing));
- spacing25 = NO;
- }
- else if (fabs(mantissa(tickInfo.labelSpacing) - 5) < 0.01)
- {
- tickInfo.labelSpacing = 2.5 *
- pow(10, orderOfMag(tickInfo.labelSpacing));
- spacing25 = YES;
- }
- }
- } // end of labelsOK loop
-
- //generate the list of formatted labels
-
- labelValue = tickInfo.firstLabel;
- for (i = 0; i < tickInfo.numberOfLabels; i++)
- {
- //put the value into a string
- if (fabs(labelValue) < tickInfo.labelSpacing * 0.01)
- labelValue = 0;
- if (tickInfo.scientific)
- {
- //put value into string
- sprintf(formatString, "%%25.%de", digits);
- sprintf(rawLabel, formatString, labelValue);
- //delete e and put exponent into separate string
- ePointer = strchr(rawLabel, 'e');
- if (*(ePointer+1) == '+')
- if (*(ePointer+2) == '0')
- strcpy(tickInfo.expString[i], ePointer+3);
- else
- strcpy(tickInfo.expString[i], ePointer+2);
- else
- {
- strcpy(tickInfo.expString[i], ePointer+1);
- if (*(ePointer+2) == '0')
- strcpy(tickInfo.expString[i]+1, ePointer+3);
- }
- *ePointer = '\0';
- }
- else
- {
- sprintf(formatString, "%%25.%df", digitsRight);
- sprintf(rawLabel, formatString, labelValue);
- }
-
- //strip off any leading blanks
- nonblank = strspn(rawLabel, " ");
- strcpy(tickInfo.labelString[i], rawLabel + nonblank);
-
- //don't use decimals with zero
- if (strspn(tickInfo.labelString[i], "0.") ==
- strlen(tickInfo.labelString[i]))
- strcpy(tickInfo.labelString[i], "0");
-
- //increment value of the label
- labelValue = labelValue + tickInfo.labelSpacing;
- }
-
- //set the ticks
- tickInfo.tickSpacing = tickInfo.labelSpacing / 5;
- for (tickInfo.firstTick = tickInfo.firstLabel,
- tickInfo.numberOfTicks = 0;
- tickInfo.firstTick - tickInfo.tickSpacing > bounds.origin.x;
- tickInfo.firstTick = tickInfo.firstTick - tickInfo.tickSpacing,
- tickInfo.numberOfTicks++);
- tickInfo.numberOfTicks = tickInfo.numberOfTicks + 5 *
- (tickInfo.numberOfLabels-1) + 1;
- for (tick = lastLabel;
- tick + tickInfo.tickSpacing < bounds.origin.x + bounds.size.width;
- tick = tick + tickInfo.tickSpacing, tickInfo.numberOfTicks++);
-
- return self;
- }
-
- - setFrame:(const NXRect *)frameRect
- {
- NXSize imageSize;
- NXRect saveBounds;
-
- saveBounds = bounds;
- [super setFrame:frameRect];
-
- [self scale:bounds.size.width/saveBounds.size.width :1];
- [self translate:bounds.origin.x - saveBounds.origin.x
- :bounds.origin.y - saveBounds.origin.y];
- if (orientation == HORIZONTAL)
- {
- if (frameRect->size.width < frameRect->size.height)
- {
- orientation = VERTICAL;
- [self rotate:90];
- [self translate:bounds.origin.x - saveBounds.origin.x
- :bounds.origin.y + saveBounds.origin.y];
- }
- }
- else
- if (frameRect->size.width >= frameRect->size.height)
- {
- orientation = HORIZONTAL;
- [self rotate:-90];
- }
-
- printf("bounds %f %f %f %f\n", bounds.origin.x, bounds.origin.y,
- bounds.size.width, bounds.size.height);
-
- imageSize = bounds.size;
- [self convertSize:&imageSize toView:nil];
-
- [numberLineNXImage free];
- numberLineNXImage = [[NXImage allocFromZone:[self zone]]
- initSize:&imageSize];
- [self calculateTicks];
- [self drawIntoImage];
- [self display];
-
- return self;
- }
-
- - read:(NXTypedStream *)typedStream
- {
- [super read:typedStream];
-
- NXReadTypes(typedStream, "iifffff", &orientation,
- &labelPosition, &linePosition, &backgroundGray, &lineGray,
- &labelGray, &borderWidth, &tickInfo);
- numberLineNXImage = NXReadObject(typedStream);
-
- return self;
- }
-
- - write:(NXTypedStream *)typedStream
- {
- [super write:typedStream];
-
- NXWriteTypes(typedStream, "iifffff", &orientation,
- &labelPosition, &linePosition, &backgroundGray, &lineGray,
- &labelGray, &borderWidth, &tickInfo);
- NXWriteObject(typedStream, numberLineNXImage);
-
- return self;
- }
-
- - awake
- {
- [super awake];
- [self calculateTicks];
-
- return self;
- }
-
- - (const char*)inspectorName
- {
- return "NumberLineInspector";
- }
-
- float mantissa(float floatValue)
- {
- return floatValue / pow(10, orderOfMag(floatValue));
- }
-
- int orderOfMag(float floatValue)
- {
- char stringValue[30], *ePointer;
-
- sprintf(stringValue, "%e", floatValue);
- ePointer = strchr(stringValue, 'e');
- return atoi(ePointer + 1);
- }
-
- int round(float floatValue)
- {
- return (int)(floatValue + 0.5);
- }
-
- @end
-